using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;

namespace PS.WebParts.Rollup.Core
{
    class AggregateInterpreter
    {
        SetOperation op;
        DataSourceDictionary dataSources;

        public AggregateInterpreter(SetOperation op, DataSourceCollection dataSources)
        {
            this.op = op;
            this.dataSources = new DataSourceDictionary(dataSources);
        }
        
        public AggregateInterpreter(SetOperation op, Dictionary<string, DataTable> dataSources)
        {
            this.op = op;
            this.dataSources = new DataSourceDictionary(dataSources);
        }
        
        public DataTable GetResults()
        {
            return Walk(op);
        }

        private DataTable Walk(SetOperation @operator)
        {
            if (@operator is Join)
            {
                return ProcessJoin(@operator);
            } else if(@operator is DataTableRef)
            {
                return dataSources[@operator.BaseId];
            }
            else if (@operator is Union)
            {
                return ProcessUnion(@operator);
            }

            throw new NotImplementedException("Operation " + @operator.GetType() + "is not yet implemented");
           // return null;
            
        }

        private DataTable ProcessUnion(SetOperation operation)
        {
            List<SetOperation> children = operation.Children;
            if (children.Count == 1)
                return Walk(children[0]);
            else
            {
                List<DataTable> tables = new List<DataTable>(children.Count);
                foreach (SetOperation child in children)
                {
                    tables.Add(Walk(child));
                }

                return JoinProcessor.Union(tables);
            }
        }

        private DataTable ProcessJoin(SetOperation @operator)
        {
            Join j = (Join) @operator;
            bool noDynCorrelation = j.Correlate == Join.CorrelationType.None;
            
            string baseId = j.BaseId;
            string joineeId = j.JoineeId;

            DataTable baseTable = dataSources[baseId];
            DataTable joineeTable;

            baseTable.Locale = null;

            ATabularDataSource childDS = dataSources.GetDataSource(joineeId);


            //See if the join is candidate for optimization by supplying parent filter values
            //directly as a part of a child Query
            //Parent is to have 25 or less unique values in its return type
            string joineeColumn = j.JoineeColumn;
            if ( (j.JoinType == Join.Type.Inner || j.JoinType == Join.Type.Left)
                && j.Correlate == Join.CorrelationType.None
                && (joineeColumn == "_ItemId"  || !joineeColumn.StartsWith("_")) )
            {
                ArrayList set;
                set = GetUniqueValues(baseTable, j.BaseColumn);
                
                //RollUpDataSource childRollupDS = childDS as RollUpDataSource;
                IJoinable childRollupDS = childDS as IJoinable;
                const string strParentFilterSig = "<ParentFilter";
                if (set.Count < 50 && set.Count !=0 && childRollupDS != null)
                {
                    string corrQuery = j.CorrelatedQuery;

                    if (string.IsNullOrEmpty(corrQuery))
                    {
                        string q = childRollupDS.Query;
                        if (string.IsNullOrEmpty(q))
                        {
                            CrossSiteDataSource crossSiteChildDs = childRollupDS as CrossSiteDataSource;
                            if (crossSiteChildDs == null || string.IsNullOrEmpty(crossSiteChildDs.ContentType))
                            {
                                corrQuery = "<Where><ParentFilter /></Where>";
                            } else
                            {
                                corrQuery = "<Where><And><ParentFilter /><Eq><FieldRef Name='ContentType'/><Value Type='Text'>"+crossSiteChildDs.ContentType+"</Value></Eq></And></Where>";
                            }
                        } else
                        {
                            corrQuery = q;
                        }
                    }

                    if (corrQuery.Contains(strParentFilterSig))
                    {
                        FieldInfo joineeField = childRollupDS.GetFields()[joineeColumn];

                        childRollupDS.Query = SubstDynQuery(corrQuery, set, 
                            joineeColumn == "_ItemId" ? "ID": joineeColumn,
                            joineeField != null && joineeField.Type != SPFieldType.Invalid ? Enum.GetName(typeof(SPFieldType), joineeField.Type) : "Text");
                    }
                }

                if (childRollupDS != null)
                {
                    string qu = childRollupDS.Query;
                    if (!string.IsNullOrEmpty(qu) && qu.Contains(strParentFilterSig))
                    {
                        int i = qu.IndexOf(strParentFilterSig);
                        int e = qu.IndexOf(">", i);

                        childRollupDS.Query = qu.Remove(i, e - i);
                    }
                }
            }

            DataRelation rel;
            DataSet dataSet = baseTable.DataSet;
            if (noDynCorrelation)
            {
                joineeTable = dataSources[joineeId];
            }
            else
            {
                joineeTable = dataSources.GetDataSource(joineeId).GetView().GetSchema();
                dataSet.Tables.Add(joineeTable);

            }

            List<string> excludedJoineeFields;
            List<string> includedJoineeFields = j.GetIncludedRightFields();

            if (includedJoineeFields.Count > 0)
            {
                excludedJoineeFields = new List<string>();
                foreach (DataColumn column in joineeTable.Columns)
                {
                    if (!includedJoineeFields.Contains(column.ColumnName))
                        excludedJoineeFields.Add(column.ColumnName);
                }
            }
            else
            {
                excludedJoineeFields = j.GetExcludedRightFields();
                if (excludedJoineeFields.Count == 1 && excludedJoineeFields[0] == "All")
                {
                    excludedJoineeFields.Clear();
                    foreach (DataColumn column in joineeTable.Columns)
                    {
                        excludedJoineeFields.Add(column.ColumnName);
                    }
                }
            }

            joineeTable.Locale = null;
            string relId = "rel" + baseId + "_" + joineeId;

            try
            {
                switch(j.JoinType)
                {
                    case Join.Type.Inner:
                        if (j.Correlate == Join.CorrelationType.Web ||
                            (baseTable.Rows.Count > 0 && joineeTable.Rows.Count > 0))
                        {
                            if (!dataSet.Relations.Contains(relId))
                            {
                                if (noDynCorrelation)
                                {
                                    DataColumn baseCol;
                                    DataColumn relCol;
                                    baseCol = SafeColumn(baseTable, j.BaseColumn);
                                    relCol = SafeColumn(joineeTable, joineeColumn);

                                    try
                                    {
                                        rel = new DataRelation(relId, baseCol, relCol, false);
                                    }
                                    catch (InvalidConstraintException ice)
                                    {
                                        throw new ApplicationException("Column types do not match: " +
                                                                       baseCol.ColumnName + ":" + baseCol.DataType + ", " +
                                                                       relCol.ColumnName + ":" + relCol.DataType, ice);
                                    }
                                }
                                else
                                    rel =
                                        new DynamicCorrelateRelation(relId, SafeColumn(baseTable, j.BaseColumn),
                                                                     SafeColumn(joineeTable, joineeColumn),
                                                                     dataSources.GetDataSource(joineeId), j.Correlate);
                                dataSet.Relations.Add(rel);
                            }
                            else
                                rel = dataSet.Relations[relId];

                            JoinProcessor.InnerJoin(baseTable, joineeTable, rel, excludedJoineeFields);
                        } else
                        {
                            baseTable.Rows.Clear();
                        }
                        break;

                    case Join.Type.Left:
                        if (j.Correlate == Join.CorrelationType.Web || joineeTable.Rows.Count > 0)
                        {
                            if (!dataSet.Relations.Contains(relId))
                            {
                                if (noDynCorrelation)
                                    rel =
                                        new DataRelation(relId, SafeColumn(baseTable, j.BaseColumn),
                                                         SafeColumn(joineeTable, joineeColumn), false);
                                else
                                    rel =
                                        new DynamicCorrelateRelation(relId, SafeColumn(baseTable, j.BaseColumn),
                                                                     SafeColumn(joineeTable, joineeColumn),
                                                                     dataSources.GetDataSource(joineeId), j.Correlate);

                                dataSet.Relations.Add(rel);
                            }
                            else
                                rel = dataSet.Relations[relId];

                            JoinProcessor.LeftJoin(baseTable, joineeTable, rel, excludedJoineeFields);
                        }
                        break;

                    case Join.Type.Right:
                        if (j.Correlate == Join.CorrelationType.Web || baseTable.Rows.Count > 0)
                        {
                            relId = "rel" + joineeId + "_" + baseId;
                            if (!dataSet.Relations.Contains(relId))
                            {
                                if (noDynCorrelation)
                                    rel =
                                        new DataRelation(relId, SafeColumn(joineeTable, joineeColumn),
                                                         SafeColumn(baseTable, j.BaseColumn), false);
                                else
                                    rel =
                                        new DynamicCorrelateRelation(relId, SafeColumn(joineeTable, joineeColumn),
                                                                     SafeColumn(baseTable, j.BaseColumn),
                                                                     dataSources.GetDataSource(joineeId), j.Correlate);
                                dataSet.Relations.Add(rel);
                            }
                            else
                                rel = dataSet.Relations[relId];

                            JoinProcessor.LeftJoin(joineeTable, baseTable, rel, excludedJoineeFields);
                        }
                        break;
                }
            }
            catch (Exception e)
            {
                throw new DataException("Error while processing join: " + j, e);
            }

            foreach (SetOperation child in @operator.Children)
            {
                Walk(child);
            }

            return dataSources[j.BaseId];
        }



        private static string SubstDynQuery(string query, ArrayList set, string joineeCol, string type)
        {
            string subst=null;
            int c = 0;

            foreach (object obj in set)
            {
                string s = "<Eq><FieldRef Name='"+joineeCol+"' /><Value Type='"+type+"'>"+obj+"</Value></Eq>";
                if ( c ++ == 0)
                {
                    subst = s;
                } else
                {
                    subst = "<Or>" + s + subst + "</Or>";
                }

                if (c > 50)
                    break;
            }

            return query.Replace("<ParentFilter />", subst).Replace("<Query>","").Replace("</Query>","");
        }

        private static ArrayList GetUniqueValues(DataTable table, string column)
        {
            DataRowCollection rows = table.Rows;
            ArrayList al = new ArrayList(rows.Count);

            foreach (DataRow row in rows)
            {
                object obj = row[column];

                if (obj == null || obj == DBNull.Value || obj.ToString().Length == 0
                    || al.Contains(obj)) continue;

                al.Add(obj);
            }

            return al;
        }

        private static DataColumn SafeColumn(DataTable table, string column)
        {
            DataColumn col = table.Columns[column];
            if (col == null)
                throw new DataSourceErrorException("Cannot locate column '" + column + "' in the table of " +
                                                   table.TableName);
            return col;
        }
    }

    public class DataSourceErrorException : ApplicationException
    {
        public DataSourceErrorException(string message) : base (message)
        {
            
        }
    }

    public class DataSourceNotSupportedException : Exception
    {
        public DataSourceNotSupportedException(Type type) : base(type.ToString())
        {
            
        }
    }
}
